﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Xml.Serialization;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BrainTechLLC.ThreadSafeObjects;
#if NO_SILVERLIGHT
using BrainTechLLC.Logging;
#endif

namespace BrainTechLLC
{
    public delegate List<PerceivedEventAndAudience> PerformCommand(CommandToPerform command, IGameThing source, IGameThing target, PropertyData args, PropertyData modifyingArguments);
    public delegate string CanPerformCommand(CommandToPerform command, IGameThing source, IGameThing target, PropertyData args, PropertyData modifyingArguments);

    [Serializable]
    [DataContract]
    public class CommandToPerform : IExecutableWorkItem, ICommandToPerform
    {
        [NonSerialized]
        public CommandToPerform AdditionalCommand;

        public CommandToPerform() { }

        public CommandToPerform(long applyToId)
        {
            ApplyToID = applyToId;
        }

        public CommandToPerform(long applyToId, string input)
        {
            ApplyToID = applyToId;
            UserInput = input;
        }

        protected PropertyData _arguments;

        public string PerformInfo
        {
            set
            {
                string[] typeAndMethod = value.Split(".".ToCharArray());
                PerformMethod = new Method(typeAndMethod[0], typeAndMethod[1]);
            }
        }

        public string CheckCanPerformInfo
        {
            set
            {
                string[] typeAndMethod = value.Split(".".ToCharArray());
                CheckPermittedMethod = new Method(typeAndMethod[0], typeAndMethod[1]);
            }
        }

        [DataMember]
        public Method PerformMethod { get; set; }

        [DataMember]
        public Method CheckPermittedMethod { get; set; }

        public PropertyData Arguments
        {
            get
            {
                if (_arguments == null)
                    Interlocked.CompareExchange<PropertyData>(ref _arguments, new PropertyData(), null);
                return _arguments;
            }
            set
            {
                _arguments = value;
            }
        }

        [NonSerialized, XmlIgnore]
        protected PropertyData _modifyingArguments;

        public long ApplyToID { get; set; }

        public int RescheduleMS { get; set; }

        [XmlIgnore]
        public object CommandOutput { get { return _commandOutput; } set { _commandOutput = value; } }

        [XmlIgnore, NonSerialized]
        protected object _commandOutput;
        [XmlIgnore, NonSerialized]
        protected DateTime _executionTime;
        [XmlIgnore, NonSerialized]
        protected bool _cancelled;
        [XmlIgnore, NonSerialized]
        protected string _input;

        [XmlIgnore]
        public DateTime ExecutionTime { get { return _executionTime; } set { _executionTime = value; } }
        [XmlIgnore]
        public bool Cancelled { get { return _cancelled; } set { _cancelled = value; } }

        [XmlIgnore]
        public string UserInput { get { return _input; } set { _input = value; } }

        [NonSerialized, XmlIgnore]
        protected State _executingState;

        [XmlIgnore]
        public State ExecutingState
        {
            get { return _executingState; }
            set { _executingState = value; }
        }

        public Delegate CheckIsPermitted
        {
            get
            {
                if (CheckPermittedMethod == null)
                    return null;

                try
                {
                    MethodInfo mi = CheckPermittedMethod.FindMethod();
                    return (CanPerformCommand)Delegate.CreateDelegate(typeof(CanPerformCommand), mi);
                }
                catch (Exception ex)
                {
#if NO_SILVERLIGHT
                    RuntimeProblemLog.LogProblem(ex.ToString());
#endif
                    throw;
                }
            }
        }

        public Delegate PerformCommand
        {
            get
            {
                try
                {
                    MethodInfo mi = PerformMethod.FindMethod();
                    return (PerformCommand)Delegate.CreateDelegate(typeof(PerformCommand), mi);
                }
                catch (Exception ex)
                {
#if NO_SILVERLIGHT
                    RuntimeProblemLog.LogProblem(ex.ToString());
#endif
                    throw;
                }
            }
        }

        [XmlIgnore]
        public PropertyData ModifyingArguments
        {
            get
            {
                if (_modifyingArguments == null)
                    Interlocked.CompareExchange<PropertyData>(ref _modifyingArguments, new PropertyData(), null);
                return _modifyingArguments;
            }
            set { _modifyingArguments = value; }
        }

        /// <summary>
        /// Performs the command
        /// </summary>
        /// <returns></returns>
        public virtual object Perform()
        {
            IGameThing source = Everything.Find<IGameThing>(ApplyToID);
            long targetID = Arguments.GetArgument<long>("TargetID", 0);
            IGameThing target = Everything.Find<IGameThing>(targetID);
            bool permitted = false;

            if (CheckIsPermitted == null)
            {
                // If there is no check permitted method, Allow the command
                permitted = true;
            }
            else
            {
                // Otherwise we must check if the command is permitted
                string msg = ((CanPerformCommand)CheckIsPermitted)(this, source, target, Arguments, ModifyingArguments);

                if (string.IsNullOrEmpty(msg))
                {
                    permitted = true;
                }
                else
                {
                    // Not permitted - show the error message to the command issuer
                    PerceivedEventAndAudience evt = PerceivedEventAndAudience.ToChar(source.AssociatedID, msg);
                    evt.Event.ToAudience(evt.Audience);
                }
            }

            if (permitted)
            {
                string reasonRestricted = string.Empty;

                // Check for restrictions
                List<Restriction> restrictions = Restriction._restrictions.Find(PerformMethod);

                if (restrictions != null)
                {
                    for (int n = 0; n < restrictions.Count; n++)
                    {
                        Restriction r = restrictions[n];
                        string s = (string)r.CheckRestriction.Invoke(source, target, Arguments);
                        if (!string.IsNullOrEmpty(s))
                        {
                            reasonRestricted = s;
                            break;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(reasonRestricted))
                {
                    // Restricted from performing the command.  Show the reason to the command issuer
                    PerceivedEventAndAudience evt = PerceivedEventAndAudience.ToChar(source.AssociatedID, reasonRestricted);
                    evt.Event.ToAudience(evt.Audience);
                }
                else
                {
                    List<PerceivedEventAndAudience> events = ((PerformCommand)PerformCommand)(this, source, target, Arguments, ModifyingArguments);

                    if (events != null)
                    {
                        // Show the resulting events to the event audience
                        foreach (PerceivedEventAndAudience evt in events)
                        {
                            if (evt.Event == null || evt.Audience.Count == 0)
                                continue;

                            evt.Event.ToAudience(evt.Audience);
                        }
                    }

                    if (AdditionalCommand != null)
                    {
                        // Allows a command to issue an additional command immediately
                        AdditionalCommand.Perform();
                        AdditionalCommand = null;
                    }
                }
                return true;
            }

            return false;
        }

        public void Execute()
        {
            if (!Cancelled)
                Perform();
        }

        public string JoinTypeAndMethodName(Method method)
        {
            return method == null ? string.Empty : JoinTypeAndMethodName(method.TypeName, method.MethodName);
        }

        public string JoinTypeAndMethodName(string typeName, string methodName)
        {
            if (!string.IsNullOrEmpty(typeName))
                return typeName + "." + methodName;
            else if (string.IsNullOrEmpty(methodName))
                return string.Empty;
            else return methodName;
        }

        public override string ToString()
        {
            string s1 = string.Empty;
            string s2 = string.Empty;

            if (CheckPermittedMethod != null)
                JoinTypeAndMethodName(CheckPermittedMethod.TypeName, CheckPermittedMethod.MethodName);

            if (PerformMethod != null)
                JoinTypeAndMethodName(PerformMethod.TypeName, PerformMethod.MethodName);

            return string.IsNullOrEmpty(s1) ? s2 : (s1 + "/" + s2);
        }
    }

    public delegate string RestrictionDelegate(IGameThing source, IGameThing target, PropertyData args);

    [DataContract]
    [Serializable]
    public class Restriction
    {
        [NonSerialized]
        public static ThreadSafeListAndLookup<Method, Restriction> _restrictions = new ThreadSafeListAndLookup<Method, Restriction>();

        [DataMember]
        public Method Restricts { get; set; }

        [DataMember]
        public DynamicMethod CheckRestriction { get; set; }

        public static void RegisterRestriction(Restriction r)
        {
            _restrictions.Add(r.Restricts, r);
        }

        [MethodVisible]
        public static Restriction CreateRestriction(Method restricts, Method restrictionMethod)
        {
            Restriction r = new Restriction() { Restricts = restricts };
            r.CheckRestriction = new DynamicMethod()
            {
                DelegateType = typeof(RestrictionDelegate),
                Method = restrictionMethod
            };
            RegisterRestriction(r);
            return r;
        }

        [MethodVisible]
        public static string MoveRestriction(IGameThing src, IGameThing tar, PropertyData args)
        {
            GameChar source = src as GameChar;
            GameChar target = tar as GameChar;

            if (source != null)
            {
                if (source.Attacking)
                    return "You are attacking an opponent!";

                if (source.BeingAttacked)
                    return "You are being attacked!";
            }

            return string.Empty;
        }

        public static void AddMoveRestrictions()
        {
            CreateRestriction(new Method("MoveCommand", "Move"), new Method(typeof(Restriction).FullName, "MoveRestriction"));
        }
    }

    public static class Helpers
    {
        public static List<T> FindUsingString<T>(this GameThingBaseClass source, string input, float threshold, FindOptions options, params Type[] types) where T : class, IGameThing
        {            
            GameLocation loc = source.GameLocation;

            if (loc == null && ((options & FindOptions.SameRoom) == FindOptions.SameRoom))
                return new List<T>();

            return loc.Things.FindUsingString<T>(input, threshold, options, types);            
        }
    }
}
